﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            ServerApp.Start();
        }
    }
    class UDPPacket
    {
        public IPEndPoint m_IP = new IPEndPoint(IPAddress.Any, 0);
        public byte[] m_Data = new byte[1024];
    }

    static class ServerApp
    {
        static UniqueIDGenerator m_UserIDGenerator = new UniqueIDGenerator();
        static public UdpClient m_Server { private set; get; }
        static Queue<PendingSendMessage> m_Messages = new Queue<PendingSendMessage>();
        public static Dictionary<uint, UserSession> m_Users = new Dictionary<uint, UserSession>();
        static Queue<PendingSendMessage> m_PendingSendPool = new Queue<PendingSendMessage>();
        public static uint m_PosSyncInterval = 6;
        public static uint TickCount { private set; get; }
        public static System.Random m_Rand = new Random();
        static List<UserSession> m_TempUsers = new List<UserSession>();
        static Queue<UDPPacket> m_PacketPool = new Queue<UDPPacket>();
        static Queue<UDPPacket> m_ChanPacket = new Queue<UDPPacket>();
        //static Queue<byte[]> m_BuffPool = new Queue<byte[]>();

        public static UnityEngine.Vector3 GetRandomPos()
        {
            return new UnityEngine.Vector3((float)(ServerApp.m_Rand.NextDouble() * 2 - 1) * 5, 0, (float)(ServerApp.m_Rand.NextDouble() * 2 - 1) * 5);
        }

        //public static byte[] TakeBuff(byte[] bs)
        //{
        //    byte[] msg;
        //    if (m_BuffPool.Count > 0)
        //    {
        //        msg = m_BuffPool.Dequeue();
        //    }
        //    else
        //    {
        //        msg = new byte[1024];
        //    }
        //    Array.Copy(bs, 0, msg, 0, bs.Length);
        //    return msg;
        //}

        //public static void PutBuff(byte[] bs)
        //{
        //    if (!m_BuffPool.Contains(bs))
        //    {
        //        m_BuffPool.Enqueue(bs);
        //    }
        //}

        public static UDPPacket TakeUDPPacket(IPEndPoint ep, byte[] bs)
        {
            UDPPacket msg;
            if (m_PacketPool.Count > 0)
            {
                msg = m_PacketPool.Dequeue();
            }
            else
            {
                msg = new UDPPacket();
            }
            msg.m_IP.Address = ep.Address;
            msg.m_IP.Port = ep.Port;
            //msg.m_Data = new byte[1024];//TakeBuff(bs);
            Array.Copy(bs, 0, msg.m_Data, 0, bs.Length);
            return msg;
        }

        public static void PutUDPPacket(UDPPacket msg)
        {
            if (msg == null)
            {
                return;
            }

            if (!m_PacketPool.Contains(msg))
            {
                //PutBuff(msg.m_Data);
                msg.m_Data = null; 
                m_PacketPool.Enqueue(msg);
            }
        }

        public static PendingSendMessage TakePendingSendMsg(EMessageType type, BaseMessage m, uint userId = 0)
        {
            PendingSendMessage msg = null;
            if (m_PendingSendPool.Count > 0)
            {
                msg = m_PendingSendPool.Dequeue();
            }
            else
            {
                msg = new PendingSendMessage();
            }
            msg.m_Type = type;
            msg.m_Msg = m;
            msg.m_UserID = userId;
            return msg;
        }

        public static void PutPendingSendMsg(PendingSendMessage msg)
        {
            if (!m_PendingSendPool.Contains(msg))
            {
                msg.m_Msg = null;
                m_PendingSendPool.Enqueue(msg);
            }
        }

        static public void Start()
        {
            LogManager.Log("server start! ");
            m_Server = new UdpClient(1234);
            m_Server.Client.ReceiveBufferSize = 1024 * 1024;
            MessageManager.Init();

            SendThread();
            // recieve
            Task.Run(() =>
            {
                IPEndPoint ep = new IPEndPoint(IPAddress.Any, 1234);
                byte[] bs = null;
                while (true)
                {
                    try
                    {
                        if (m_Server.Available > 0)
                        {
                            bs = m_Server.Receive(ref ep);
                            m_ChanPacket.Enqueue(TakeUDPPacket(ep, bs));
                        }
                    }
                    catch (Exception ex)
                    {
                        LogManager.Error(ex.Message);
                    }
                    //Thread.Sleep(1);
                }
            });
            ProcessReceive(); 

            // main thread
            while (true)
            {
                IterateUsers((user) =>
                {
                    user.Tick();
                });
                TickCount++;
                Thread.Sleep(33);
            }
        }

        static async void SendThread()
        {
            await Task.Run(() =>
            {
                PendingSendMessage msg;
                byte[] sendBytes;
                UserSession user;
                while (true)
                {
                    try
                    {
                        lock (m_Messages)
                        {
                            //LogManager.Log("send=");
                            while (m_Messages.Count != 0)
                            {
                                msg = m_Messages.Dequeue();
                                if (msg.m_Type == EMessageType.SYNC)
                                {
                                    //LogManager.Log("send sync pos" + (msg.m_Msg as S2C_SyncMessage).m_Pos);
                                }
                                sendBytes = MessageManager.Pack(msg.m_Type, msg.m_Msg);
                                // broadcast
                                if (msg.m_UserID == 0)
                                {
                                    IterateUsers((item) =>
                                    {
                                        if (!HideLog(msg.m_Type))
                                        {
                                            LogManager.Log("send " + msg.m_Type + ", id=" + item.m_UserID);
                                        }
                                        m_Server.Send(sendBytes, sendBytes.Length, item.m_RemoteIP);
                                    });
                                }
                                else
                                {
                                    if (m_Users.ContainsKey(msg.m_UserID))
                                    {
                                        if (!HideLog(msg.m_Type))
                                        {
                                            LogManager.Log("send " + msg.m_Type + ", id=" + msg.m_UserID);
                                        }
                                        user = m_Users[msg.m_UserID];
                                        m_Server.Send(sendBytes, sendBytes.Length, user.m_RemoteIP);
                                    }
                                }
                                PutPendingSendMsg(msg);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log("send thread" + ex.Message);
                    }
                    Thread.Sleep(1);
                }
            });
        }
        static async void ProcessReceive()
        {
            await Task.Run(() =>
            {
                IPEndPoint ep;
                uint id;
                while (true)
                {
                    while (m_ChanPacket.Count > 0)
                    {
                        var p = m_ChanPacket.Dequeue();
                        if (p == null)
                        {
                            //continue;
                        }

                        ep = p.m_IP;
                        if (!ExistUser(ep))
                        {
                            id = m_UserIDGenerator.GetUniqueID();
                            m_Users.Add(id, new UserSession(ep, id));
                            OnUsersChanged();
                        }

                        UserSession user;
                        for (int i = m_TempUsers.Count - 1; i >= 0; i--)
                        {
                            user = m_TempUsers[i];
                            if (user == null)
                            {
                                continue;
                            }
                            if (MessageManager.IsRemoteIPIdentity(user.m_RemoteIP, ep))
                            {
                                //user.ReceiveMessage(TakeBuff(p.m_Data));
                                user.ReceiveMessage(p.m_Data);
                                break;
                            }
                        }
                        //PutUDPPacket(p);
                    }
                    Thread.Sleep(1);
                }
            });
        }

        static bool ExistUser(IPEndPoint ep)
        {
            bool rs = false;
            IterateUsers((user) =>
            {
                if (MessageManager.IsRemoteIPIdentity(user.m_RemoteIP, ep))
                {
                    rs = true;
                }
            });
            return rs;
        }

        static public void Send(EMessageType type, BaseMessage msg, uint userId = 0)
        {
            lock (m_Messages)
            {
                m_Messages.Enqueue(TakePendingSendMsg(type, msg, userId));
            }
        }

        public static void StopUser(UserSession user)
        {
            if (user == null)
            {
                return;
            }
            m_Users.Remove(user.m_UserID);
            user.Stop();
            OnUsersChanged();
            GC.Collect();
        }

        static void OnUsersChanged()
        {
            //lock (m_TempUsers)
            {
                m_TempUsers.Clear();
                m_TempUsers.AddRange(m_Users.Values);
            }
        }

        public static void IterateUsers(Action<UserSession> action)
        {
            if (action == null)
            {
                return;
            }
            //lock (m_TempUsers)
            {
                for (int i = m_TempUsers.Count - 1; i >= 0; i--)
                {
                    action(m_TempUsers[i]);
                }
            }
        }

        public static bool HideLog(EMessageType type)
        {
            return false;
            //return type == EMessageType.PING || type == EMessageType.SYNC;
        }
    }
}
